package main

import (
	"fmt"
	"hash/crc32"
	"math/rand"
	"sort"
	"strconv"
	"sync"
	"time"
)

func main() {
	nodes := []*node{
		{"172.16.0.7:3500", 1, 1, 0},
		{"172.16.0.8:3500", 2, 2, 0},
		{"172.16.0.9:3500", 3, 3, 0},
	}

	servers := &serNode{
		ads:   nodes,
		count: 0,
	}
	rand.Seed(time.Now().Unix())
	fmt.Println("随机负载均衡：")
	fmt.Println(servers.Random())
	fmt.Println(servers.Random())
	fmt.Println(servers.Random())

	fmt.Println("轮询负载均衡：")
	fmt.Println(servers.Round())
	fmt.Println(servers.Round())
	fmt.Println(servers.Round())

	fmt.Println("加权轮询负载均衡：")
	fmt.Println(servers.WeightRound())
	fmt.Println(servers.WeightRound())
	fmt.Println(servers.WeightRound())

	fmt.Println("一致性hash")
	err := servers.NewConsistHash(100, nil)
	if err != nil {
		panic(err)
	}
	for i := 0; i < 10; i++ {
		fmt.Println(servers.GetCH("test" + strconv.Itoa(i*10)))
	}
}

type serNode struct {
	ads   []*node
	count int

	// consist hash相关
	ring       []uint32
	mapNode    map[uint32]string
	hash       func([]byte) uint32
	mu         sync.RWMutex
	vitualNode int
}

type node struct {
	addr            string
	weight          int // 节点权重
	currentWeight   int // 节点临时权重，每轮都会变化
	effectiveWeight int // 有效权重, 默认与weight相同 , totalWeight = sum(effectiveWeight)  //出现故障就-1
}
type Hash func([]byte) uint32

// 随机
func (n *serNode) Random() string {
	ind := rand.Intn(len(n.ads))
	return n.ads[ind].addr
}

// 轮询
func (n *serNode) Round() string {
	ind := n.count % len(n.ads)
	n.count++
	return n.ads[ind].addr
}

// WeightRound 加权轮询
/*
通过currentw + eff 保证加权分配
通过currentw - sum(eff) 保证了轮询
*/
func (n *serNode) WeightRound() string {
	totalWeight := 0
	ind := 0
	for i, v := range n.ads {
		totalWeight += v.effectiveWeight

		// 临时权重加上eff有效权重才算是当前节点的真实权重
		n.ads[i].currentWeight += n.ads[i].effectiveWeight

		// effectiveWeight不等于weight的时候，则表示出现过故障
		if n.ads[i].effectiveWeight < n.ads[i].weight {
			n.ads[i].effectiveWeight++
		}

		// 选出最大权重的节点
		if n.ads[ind].currentWeight < n.ads[i].currentWeight {
			ind = i
		}
	}

	// 当前节点命中后，则需要把当前临时权重减小
	n.ads[ind].currentWeight -= totalWeight

	return n.ads[ind].addr
}

// 一致性hash
// https://xie.infoq.cn/article/78043810ecc807d1896c6f3f2
// 一致性hash
/* 通过一个环来映射。把一致性hash计算后的所有结果，都看做一个hash闭环，0与2^32重合。
将服务的IP去hash得到一个hash值，落在hash环内。
然后，把数据的key（比如请求ID），通过相同的hash算法，计算，也落入环中。
key的位置顺时针移动，找到的最近的节点就是命中返回的。
*/
// 传入虚拟节点数量与hash函数
func (n *serNode) NewConsistHash(v int, fn Hash) error {
	n.mapNode = make(map[uint32]string)
	if fn != nil {
		n.hash = fn
	} else {
		n.hash = crc32.ChecksumIEEE
	}

	n.vitualNode = v

	// 将节点与虚拟节点都加入环中
	for i, a := range n.ads {
		adr := a.addr
		for j := 0; j < v; j++ {
			key := n.hash([]byte(adr + strconv.Itoa(i)))
			n.ring = append(n.ring, key)
			n.mapNode[key] = adr
		}
	}

	// 将环排序
	sort.Slice(n.ring, func(i, j int) bool {
		return n.ring[i] < n.ring[j]
	})

	return nil
}

// 通过传入key计算hash值，随后查找环中最近的一个节点
func (n *serNode) GetCH(key string) string {
	hashVal := n.hash([]byte(key))
	ind := sort.Search(len(n.ring), func(i int) bool {
		return n.ring[i] >= hashVal
	})

	//if ind < len(n.ring) {
	//	if ind == len(n.ring)-1 {
	//		return n.mapNode[n.ring[0]]
	//	} else {
	//		return n.mapNode[n.ring[ind]]
	//	}
	//} else {
	//	return n.mapNode[n.ring[len(n.ring)-1]]
	//}

	if ind == len(n.ring) {
		ind = 0
	}

	return n.mapNode[n.ring[ind]]
}
